﻿using KSASupport.Calibration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Xml.Linq;

namespace KSASupport
{
    /// <summary>
    /// There are several ways to calibrate (with the possibilities of more being added). This enum tracks the method desired.
    /// Based on this setting, the calibration portion of the DepthManager will choose which methods to call in the DepthCalibrationManager to calibrate the system
    /// </summary>
    public enum CalibrationMethods { Plane, PerPixel}
    /// <summary>
    /// Designed to store settings which each Depthmanager will need. This is mainly to reduce having a ton of settings in the
    /// DepthManager class, which already has alot on its plate. This stores the data as xml. Touch mapping, calibration, and depth settings are all stored here
    /// </summary>
    public class DepthManagerSettings
    {
        // The 'plane' method is experimental, and wasn't better than the perpixel method at the time
        // I wrote this. 
        public CalibrationMethods CalibrationMethod = CalibrationMethods.PerPixel;
        public int MinDepthSetting;
        public int MaxDepthSetting;
        public int MinimumBlobPixelCount;

        public int DepthResolutionWidth = 320; // Remember, Magic Numbers are bad!  We'll store the resolution here and reference it everywhere else.
        public int DepthResolutionHeight = 240;// Note that if you want to change the depth resolution, change it here, and in the .DepthStream.Enable call!

        XDocument xDoc;

        #region Touch Mapping Values
        public Point CalibrationTopLeftSource1          =   new Point(0, 0);
        public Point CalibrationBottomLeftSource1       =   new Point(0, 0);
        public Point CalibrationBottomRightSource1      =   new Point(0, 0);
        public Point CalibrationTopRightSource1         =   new Point(0, 0);

        public Point CalibrationTopLeftDestination1     =   new Point(0, 0);
        public Point CalibrationBottomLeftDestination1  =   new Point(0, 0);
        public Point CalibrationBottomRightDestination1 =   new Point(0, 0);
        public Point CalibrationTopRightDestination1    =   new Point(0, 0);

        // Repeat Calibration points for 'other side' calibration
        public Point CalibrationTopLeftSource2          = new Point(0, 0);
        public Point CalibrationBottomLeftSource2       = new Point(0, 0);
        public Point CalibrationBottomRightSource2      = new Point(0, 0);
        public Point CalibrationTopRightSource2         = new Point(0, 0);

        public Point CalibrationTopLeftDestination2     = new Point(0, 0);
        public Point CalibrationBottomLeftDestination2  = new Point(0, 0);
        public Point CalibrationBottomRightDestination2 = new Point(0, 0);
        public Point CalibrationTopRightDestination2    = new Point(0, 0);
        #endregion

        #region DepthSettings
        public double LowestDepthFromSurface    = 12;
        public double HighestDepthFromSurface   = 20;
        #endregion


        /// <summary>
        /// This Rect contains a start and stop point which defines the screen that we're detecting contacts from. The Kinect will see a greater
        /// area than the screen itself, so this helps us only detect touches from the screen. We also calculate x/y coordinates based on this.
        /// </summary>
        public Rect ScreenBoundaryRect;


        public CalibrationPixel[] calibrationDistanceValues;


        public Point TopLeftCalibrationDepthPoint       = new Point();
        public Point BottomLeftCalibrationDepthPoint    = new Point();
        public Point TopRightCalibrationDepthPoint      = new Point();
        public Point BottomRightCalibrationDepthPoint   = new Point();

        // Calibration file values
        string KSAConfigurationDirectory;
        string KSAConfigFileName;
        string KSAConfigFullPath;

        /// <summary>
        /// New instance of Depthmanagerettings class
        /// </summary>
        /// <param name="ConfigurationName">name of this config.</param>
        /// <param name="ConfigWidth">Width of the depth image</param>
        /// <param name="ConfigHeight">height of the depth image</param>
        public DepthManagerSettings(string ConfigurationName,int ConfigWidth,int ConfigHeight)
        { 
            // Note: These default settings are just so we have 'something' to start with. 
            MinDepthSetting         = 6;
            MaxDepthSetting         = 13;
            MinimumBlobPixelCount   = 8;

            KSAConfigFileName = ConfigurationName;
            DepthResolutionHeight = ConfigHeight;
            DepthResolutionWidth = ConfigWidth;
            ScreenBoundaryRect = new Rect(new Point(0, 0), new Point(DepthResolutionWidth, DepthResolutionHeight));
            calibrationDistanceValues = new CalibrationPixel[DepthResolutionHeight * DepthResolutionWidth];

            KSAConfigurationDirectory = Environment.ExpandEnvironmentVariables("%ALLUSERSPROFILE%") + @"\KinectScreenAware\";
            KSAConfigFullPath = KSAConfigurationDirectory + KSAConfigFileName;

        }
        /// <summary>
        /// Saves the depth manager settings to xml
        /// </summary>
        /// <returns></returns>
        public bool SaveSettings()
        {
            try
            {
                xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
                 new XElement("Calibration",
                    // Calibration touch points from the Kinect
                     new XElement("CalibrationTopLeftDestination1", CalibrationTopLeftDestination1),
                     new XElement("CalibrationBottomLeftDestination1", CalibrationBottomLeftDestination1),
                     new XElement("CalibrationBottomRightDestination1", CalibrationBottomRightDestination1),
                     new XElement("CalibrationTopRightDestination1", CalibrationTopRightDestination1),
                    // Calibration touch points to the screen
                     new XElement("CalibrationTopLeftSource1", CalibrationTopLeftSource1),
                     new XElement("CalibrationBottomLeftSource1", CalibrationBottomLeftSource1),
                     new XElement("CalibrationBottomRightSource1", CalibrationBottomRightSource1),
                     new XElement("CalibrationTopRightSource1", CalibrationTopRightSource1),
                    // Calibration touch points from the Kinect (Reverse side)
                     new XElement("CalibrationTopLeftDestination2", CalibrationTopLeftDestination2),
                     new XElement("CalibrationBottomLeftDestination2", CalibrationBottomLeftDestination2),
                     new XElement("CalibrationBottomRightDestination2", CalibrationBottomRightDestination2),
                     new XElement("CalibrationTopRightDestination2", CalibrationTopRightDestination2),
                    // Calibration touch points to the screen (Reverse side)
                     new XElement("CalibrationTopLeftSource2", CalibrationTopLeftSource2),
                     new XElement("CalibrationBottomLeftSource2", CalibrationBottomLeftSource2),
                     new XElement("CalibrationBottomRightSource2", CalibrationBottomRightSource2),
                     new XElement("CalibrationTopRightSource2", CalibrationTopRightSource2),
                     // Depth Thresholds
                     new XElement("LowestDepthFromSurface", LowestDepthFromSurface),
                     new XElement("HighestDepthFromSurface", HighestDepthFromSurface),
                     // Blob properties
                     new XElement("MinimumBlobPixelCount", MinimumBlobPixelCount),
                     //ScreenBoundaryRect
                     new XElement("BoundingRectTopLeft", ScreenBoundaryRect.TopLeft),
                     new XElement("BoundingRectBottomRight", ScreenBoundaryRect.BottomRight),
                    // Depth Calibration Values
                     new XElement("CalibrationDistanceValues", SerializeCalibrationDistanceValues())));

                if (!Directory.Exists(KSAConfigurationDirectory))
                {
                    Directory.CreateDirectory(KSAConfigurationDirectory);
                }
   
                xDoc.Save(KSAConfigurationDirectory + KSAConfigFileName);

                return true;
            }
            catch (Exception)
            {
                return false;
            }

        }

        
        /// <summary>
        /// Reads in the current calibration settings
        /// </summary>
        /// <returns>True if successful, False if anything goes wrong</returns>
        public bool ReadSettings()
        {
            try
            {
                XDocument xDoc = XDocument.Load(KSAConfigFullPath);

                CalibrationTopLeftSource1 = Point.Parse(xDoc.Descendants("CalibrationTopLeftSource1").Single().Value);
                CalibrationBottomLeftSource1 = Point.Parse(xDoc.Descendants("CalibrationBottomLeftSource1").Single().Value);
                CalibrationBottomRightSource1 = Point.Parse(xDoc.Descendants("CalibrationBottomRightSource1").Single().Value);
                CalibrationTopRightSource1 = Point.Parse(xDoc.Descendants("CalibrationTopRightSource1").Single().Value);

                CalibrationTopLeftDestination1 = Point.Parse(xDoc.Descendants("CalibrationTopLeftDestination1").Single().Value);
                CalibrationBottomLeftDestination1 = Point.Parse(xDoc.Descendants("CalibrationBottomLeftDestination1").Single().Value);
                CalibrationBottomRightDestination1 = Point.Parse(xDoc.Descendants("CalibrationBottomRightDestination1").Single().Value);
                CalibrationTopRightDestination1 = Point.Parse(xDoc.Descendants("CalibrationTopRightDestination1").Single().Value);

                CalibrationTopLeftSource2 = Point.Parse(xDoc.Descendants("CalibrationTopLeftSource2").Single().Value);
                CalibrationBottomLeftSource2 = Point.Parse(xDoc.Descendants("CalibrationBottomLeftSource2").Single().Value);
                CalibrationBottomRightSource2 = Point.Parse(xDoc.Descendants("CalibrationBottomRightSource2").Single().Value);
                CalibrationTopRightSource2 = Point.Parse(xDoc.Descendants("CalibrationTopRightSource2").Single().Value);

                CalibrationTopLeftDestination2 = Point.Parse(xDoc.Descendants("CalibrationTopLeftDestination2").Single().Value);
                CalibrationBottomLeftDestination2 = Point.Parse(xDoc.Descendants("CalibrationBottomLeftDestination2").Single().Value);
                CalibrationBottomRightDestination2 = Point.Parse(xDoc.Descendants("CalibrationBottomRightDestination2").Single().Value);
                CalibrationTopRightDestination2 = Point.Parse(xDoc.Descendants("CalibrationTopRightDestination2").Single().Value);

                LowestDepthFromSurface = double.Parse(xDoc.Descendants("LowestDepthFromSurface").Single().Value);
                HighestDepthFromSurface = double.Parse(xDoc.Descendants("HighestDepthFromSurface").Single().Value);

                MinimumBlobPixelCount = int.Parse(xDoc.Descendants("MinimumBlobPixelCount").Single().Value);


                Point BoundingRectTopLeft = Point.Parse(xDoc.Descendants("BoundingRectTopLeft").Single().Value);
                Point BoundingRectBottomRight = Point.Parse(xDoc.Descendants("BoundingRectBottomRight").Single().Value);
                ScreenBoundaryRect = new Rect(BoundingRectTopLeft, BoundingRectBottomRight);

                string CalibrationPoints = xDoc.Descendants("CalibrationDistanceValues").Single().Value;
                DeSerializeCalibrationDistanceValues(CalibrationPoints);

                return true;
            }
            catch (Exception exc)
            {
                System.Diagnostics.Debug.WriteLine("Calibration failed to Load!" + exc.Message);
                return false;
            }
        }



        #region private implementation

        private void DeSerializeCalibrationDistanceValues(string seralizedValues)
        {
            calibrationDistanceValues = new CalibrationPixel[DepthResolutionWidth * DepthResolutionHeight];
            string[] values = seralizedValues.Split(' ');
            for (int i = 0; i < values.Count() - 1; i++)
            {
                string val = values[i];
                if (val != "N")
                {
                    CalibrationPixel pixel = new CalibrationPixel(int.Parse(val),this);
                    calibrationDistanceValues[i] = pixel;
                }
                else
                {
                    calibrationDistanceValues[i] = null;
                }
            }
        }

        private string SerializeCalibrationDistanceValues()
        {

            StringBuilder calibrationValues = new StringBuilder(DepthResolutionWidth * DepthResolutionHeight * 2);
            foreach (CalibrationPixel calibrationPixel in calibrationDistanceValues)
            {
                if (calibrationPixel != null)
                {
                    calibrationValues.Append(calibrationPixel.Value + " ");
                }
                else
                {
                    calibrationValues.Append("N ");
                }
            }
            return calibrationValues.ToString();
        }



        /// <summary>
        /// Code stub to try to clean up value loading in the future
        /// </summary>
        /// <param name="ValueName"></param>
        /// <returns></returns>
        private object GetValueFromSettings(string ValueName)
        {
            xDoc = XDocument.Load(KSAConfigFullPath);
            object value = xDoc.Descendants(ValueName).Single().Value;
            return value;
        }

        #endregion

    }
}
